package coder.user.service.impl;

import coder.common.BaseContext;
import coder.exception.BaseException;
import coder.user.dto.UserDto;
import coder.user.entity.User;
import coder.user.mapper.UserMapper;
import coder.user.service.IUserService;
import coder.utils.ConfigData;
import coder.utils.JwtUtils;
import coder.utils.RedisUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Autowired
    private RedisUtils redisUtils;
    @Override
    public void saveUser(User user) {
        // 验证参数
        verifyUserParams(user, true);
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getUsername, user.getUsername());
        if(this.getOne(queryWrapper) != null) {
            throw new BaseException("用户名已存在");
        }
        // 设置初始密码
        user.setPassword(DigestUtils.md5DigestAsHex(ConfigData.INIT_PASSWORD.getBytes()));
        this.save(user);
    }

    /**
     * 用户登录方法
     *
     * 该方法实现用户登录功能，包括验证用户输入参数、查询用户信息、生成JWT令牌等步骤
     *
     * @param user 用户实体，包含用户名和密码
     * @return 返回用户信息DTO，包括用户基本信息和登录令牌
     * @throws BaseException 当用户名或密码错误时抛出异常
     */
    @Override
    public UserDto login(User user) {
        // 验证用户参数，确保用户输入合法
        verifyUserParams(user, true);

        // 创建用户DTO对象，用于存储用户信息和登录令牌

        String redisKey = "user_" + user.getUsername();
        // 先去redis中取
        UserDto redisUser = (UserDto) redisUtils.get(redisKey);
        log.info("redisUser: " + redisUser);
        if(redisUser != null) {
            redisUser.setLoginTime(LocalDateTime.now());
            return redisUser;
        }

        // 创建查询条件构造器
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();

        // 将用户输入的密码进行MD5加密，以保证密码传输的安全性
        String md5Password = DigestUtils.md5DigestAsHex(user.getPassword().getBytes());

        // 设置查询条件：密码等于加密后的密码
        queryWrapper.eq(User::getPassword, md5Password);
        // 设置查询条件：用户名等于用户输入的用户名
        queryWrapper.eq(User::getUsername, user.getUsername());

        // 执行查询，获取用户信息
        User result = this.getOne(queryWrapper);

        // 如果查询结果为空，说明用户名或密码错误，抛出异常
        if(result == null) {
            throw new BaseException("用户名或密码错误");
        }
        // 更新最近一次登录时间
        result.setLoginTime(LocalDateTime.now());
        this.updateById(result);
        // 为了安全考虑，移除用户信息中的密码字段
        result.setPassword(null);
        UserDto userDto = new UserDto();
        // 将用户信息复制到DTO对象中
        BeanUtils.copyProperties(result, userDto);

        // 创建存储JWT claims的Map对象
        Map<String,Object> claims = new HashMap<>();
        // 将用户ID和用户名添加到claims中，这些信息将用于生成JWT
        claims.put("id", result.getId());
        claims.put("username",result.getUsername());

        // 生成JWT令牌
        String token = JwtUtils.generateJwt(claims);

        // 将生成的JWT令牌添加到用户DTO中
        userDto.setToken(token);

        redisUtils.set(redisKey, userDto);

        // 返回用户DTO对象
        return userDto;
    }

    @Override
    public void updateUser(User user) {
        this.updateById(user);
        Set<String> keys = redisUtils.getKeys("user_" + user.getUsername());
        for (String key : keys) {
            redisUtils.delete(key);
        }
    }

    /**
     * 重写以更改用户密码
     *
     * @param id 用户ID
     * @param oldPwd 原始密码
     * @param newPwd 新密码
     * @throws BaseException 如果用户不存在或原始密码错误
     */
    @Override
    public void changeUserPwd(String id, String oldPwd, String newPwd) {
        // 创建查询包装器以根据ID查找用户
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getId, id);
        // 获取用户对象
        User targetUser = this.getOne(queryWrapper);
        // 检查用户是否存在
        if(targetUser == null) {
            throw  new BaseException("用户不存在");
        }
        // 校验原始密码
        queryWrapper.eq(User::getPassword, DigestUtils.md5DigestAsHex(oldPwd.getBytes()));
        if(this.getOne(queryWrapper) == null) {
            throw new BaseException("原密码错误");
        }
        // 加密新密码
        String newMd5Pwd = DigestUtils.md5DigestAsHex(newPwd.getBytes());
        // 创建一个新用户对象用于更新密码
        User updateUser = new User();
        updateUser.setId(id);
        updateUser.setPassword(newMd5Pwd);
        // 更新用户密码
        this.updateById(updateUser);
        // 清除Redis中该用户的缓存信息
        Set<String> keys = redisUtils.getKeys("user_" + targetUser.getUsername());
        for (String key : keys) {
            redisUtils.delete(key);
        }
    }

    /**
     * 根据用户名查询用户信息
     * 首先尝试从Redis中获取用户数据，如果未找到，则从数据库中查询
     * 这种方式旨在减少数据库的访问压力，提高查询效率
     *
     * @param username 用户名，用于查询用户信息
     * @return User对象，包含用户信息如果未找到用户，则返回null
     */
    @Override
    public User queryOne(String username) {
        // 记录查询日志
        log.info("查询用户：" + username);

        // 构造Redis键
        String redisKey = "user_" + username;

        // 从Redis中获取用户信息
        User redisUser = (User) redisUtils.get(redisKey);
        User user = new User();

        // 如果Redis中存在用户信息，则复制属性并返回
        if(redisUser != null) {
            BeanUtils.copyProperties(redisUser, user);
            return user;
        }

        // 如果Redis中不存在用户信息，则从数据库中查询
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getUsername, username);

        // 返回查询到的用户信息
        return this.getOne(queryWrapper);
    }

    public void verifyUserParams(User user , boolean needPassword) {
        if(user.getUsername() == null) {
            throw new BaseException("用户名不能为空");
        }
        if(needPassword && user.getPassword() == null) {
            throw new BaseException("密码不能为空");
        }
    }
}
